#ifndef UTILITY_HPP
#define UTILITY_HPP

#include "config.hpp"
#include "pcb.hpp"
#include "rander.hpp"
#include "state.hpp"
#include <array>
#include <cstddef>
#include <iostream>
#include <vector>

void print_sep();
// ---------------------------------------------------------
template <typename T, size_t N>
bool arr_le(std::array<T, N> a1, std::array<T, N> a2) {
  for (size_t i = 0; i < N; ++i) {
    if (a1[i] > a2[i]) {
      return false;
    }
  }
  return true;
}

// ---------------------------------------------------------

template <size_t NRESOURCE> void print_resource_name() {
  for (size_t i = 0; i < NRESOURCE; ++i) {
    std::cout << char(65 + i) << ' ';
  }
}

template <size_t NRESOURCE>
void print_resource_val(std::array<size_t, NRESOURCE> &resources) {
  for (size_t i = 0; i < NRESOURCE; ++i) {
    std::cout << resources[i] << ' ';
  }
}

template <size_t NRESOURCE>
void print_proc_need_allocation(pcb<NRESOURCE> &pcb) {
  for (auto &n : pcb.need) {
    std::cout << n << " ";
  }
  std::cout << "\t\t";

  for (auto &n : pcb.allocation) {
    std::cout << n << " ";
  }
  std::cout << "\t\t";
}

template <size_t NRESOURCE> void print_proc_resource(pcb<NRESOURCE> &pcb) {
  for (auto &n : pcb.max) {
    std::cout << n << " ";
  }
  std::cout << "\t\t";

  print_proc_need_allocation(pcb);
}

template <size_t NPROC, size_t NRESOURCE>
void print_init(std::array<pcb<NRESOURCE>, NPROC> &proc,
                std::array<size_t, NRESOURCE> &resources) {
  std::cout << "initialized:\n";
  print_sep();
  std::cout << "\tMax\t\tNeed\t\tAllocation\tAvailable\n\t";
  for (int i = 0; i < 4; ++i) {
    print_resource_name<NRESOURCE>();
    std::cout << "\t\t";
  }
  std::cout << "\n";
  for (size_t i = 0; i < NPROC; ++i) {
    std::cout << 'P' << i << '\t';
    print_proc_resource(proc[i]);
    if (!i) {
      print_resource_val(resources);
    }
    std::cout << '\n';
  }
  print_sep();
}

// ---------------------------------------------------------
template <size_t NRESOURCE>
void print_states(std::vector<state<NRESOURCE>> states) {

  //   std::cout << "\tMax\t\tAllocation\tNeed\t\tAvailable\n\t";
  std::cout << "\tWork\t\tNeed\t\tAllocation\tWork+allocation\tFinish\n\t";
  for (int i = 0; i <= 4; ++i) {
    print_resource_name<NRESOURCE>();
    std::cout << "\t\t";
  }
  std::cout << "\n";
  for (size_t i = 0; i < states.size(); ++i) {
    std::cout << 'P' << states[i].idx << '\t';
    print_resource_val(states[i].work);
    std::cout << "\t\t";
    print_proc_need_allocation(states[i].pcb_val);
    // print_proc_resource(proc[i]);
    print_resource_val(states[i].work_allocation);
    std::cout << "\t\ttrue";
    std::cout << '\n';
  }
}

// ---------------------------------------------------------

template <size_t NPROC, size_t NRESOURCE>
void init(std::array<pcb<NRESOURCE>, NPROC> &proc,
          std::array<size_t, NRESOURCE> &resources) {
  rander resource_rander(MIN_RESOURCE, MAX_RESOURCE);

  for (size_t i = 0; i < NRESOURCE; ++i) {
    resources[i] = resource_rander.get_rand();
#ifdef DEBUG
    std::cout << resources[i] << "\n";
#endif
  }

  for (auto &p : proc) {
    init_proc(p, resources);
  }
  //   print_init(proc, resources);
}

template <size_t N>
void init_proc(pcb<N> &proc, std::array<size_t, N> &resources) {
  for (size_t i = 0; i < N; ++i) {
    rander max_rander(0, resources[i]);
    proc.max[i] = max_rander.get_rand();

    rander allocation_rander(0, proc.max[i]);
    proc.allocation[i] = allocation_rander.get_rand();

    proc.need[i] = proc.max[i] - proc.allocation[i];

    resources[i] -= proc.allocation[i];
  }
}

// ---------------------------------------------------------

template <size_t NPROC, size_t NRESOURCE>
std::vector<size_t>
find_possible_idx(std::array<pcb<NRESOURCE>, NPROC> &proc,
                  std::array<size_t, NRESOURCE> &resources) {
  std::vector<size_t> res;
  for (size_t i = 0; i < NPROC; ++i) {
    if (arr_le(proc[i].need, resources)) {
      res.push_back(i);
#ifdef DEBUG
      std::cout << i << "\n";
#endif
    }
  }
  return res;
}

template <size_t NPROC, size_t NRESOURCE>
std::vector<state<NRESOURCE>>
try_alloc(size_t idx, std::array<pcb<NRESOURCE>, NPROC> proc,
          std::array<size_t, NRESOURCE> resources) {

  std::vector<state<NRESOURCE>> res;
  std::array<bool, NPROC> finish;
  for (auto &item : finish) {
    item = false;
  }
  while (true) {
    state<NRESOURCE> item;
    item.idx = idx;
    item.pcb_val = proc[idx];
    item.work = resources;
    for (size_t i = 0; i < NRESOURCE; ++i) {
      resources[i] += proc[idx].allocation[i];
    }
    item.work_allocation = resources;
    item.finish = true;

    finish[idx] = true;
    res.push_back(item);

    for (size_t i = 0; i < NPROC; ++i) {
      if (arr_le(proc[i].need, resources) && finish[i] == false) {
        idx = i;
        break;
      } else {
        if (i == NPROC - 1) {
          return res;
        }
      }
    }
  }
}

template <size_t NPROC, size_t NRESOURCE>
bool is_safe(std::array<pcb<NRESOURCE>, NPROC> &proc,
             std::array<size_t, NRESOURCE> &resources) {
  auto possibele_idx = std::move(find_possible_idx(proc, resources));
  for (auto idx : possibele_idx) {
    auto alloc_res = try_alloc(idx, proc, resources);
    if (alloc_res.size() == NPROC) {
      print_states(alloc_res);
      print_sep();
      std::cout << "safe sequence found: ";
      for (auto &item : alloc_res) {
        std::cout << "P" << item.idx << " ";
      }
      std::cout << "\n";
      return true;
    }
  }
  return false;
}

// ---------------------------------------------------------

template <size_t NPROC, size_t NRESOURCE>
bool is_request_safe(size_t idx, std::array<size_t, NRESOURCE> &request,
                     std::array<pcb<NRESOURCE>, NPROC> proc,
                     std::array<size_t, NRESOURCE> resources) {
  if (!arr_le(request, resources)) {
    return false;
  }

  for (size_t i = 0; i < NRESOURCE; ++i) {
    proc[idx].allocation[i] += request[i];
    proc[idx].need[i] -= request[i];
    resources[i] -= request[i];
  }

  return is_safe(proc, resources);
}

#endif